// const _ = require('lodash');
import _ from 'lodash'
import fp from 'lodash/fp'

function fn(...args) {
  console.log(args.reverse())
}

// fn(1, 2, 3, 4, 5)

/* 
  ### 为什么要学习函数式编程 ？
  1.函数式编程随着React流行越来越受欢迎
  2.vue3开始拥抱函数式编程
  3.可以抛弃掉烦人的this
  4.打包时可以使用tree shaking过滤掉无用代码
  5.方便测试，方便并行处理
  6.有很多库帮助我们进行函数式处理。如lodash、underscore
*/

/* 
### 什么是函数式编程？
函数式编程(FP)，FP编程范式之一。范式编程还有面向过程编程、面向对象编程
函数式编程中的函数不是程序中的函数（方法），而是数学中的函数即映射关系
函数式编程不会保留计算中得结果，所以变量是不可变得（无状态得）
*/


/* 
### 高阶函数：函数作为参数，函数作为返回值
*/


// 函数作为参数   实例
const arr = [1, 2, 3, 4, 5, 6]

function forEach(array, fn) {
  for (let i = 0; i < array.length; i++) {
    fn(arr[i])
  }
}
// forEach(arr, function (v) {
//   console.log(v)
// })

function filter(arr, fn) {
  let result = [];
  for (let i = 0, l = arr.length; i < l; i++) {
    if (fn(arr[i])) {
      result.push(arr[i])
    }
  }
  return result;
}

let r = filter(arr, function (v) {
  return v % 2 !== 0;
})
// console.log(r)


// 函数作为返回值  // 实例
function once(fn) {
  let done = false;
  return function (...args) {
    if (!done) {
      done = true;
      return fn.apply(null, args);
    }
  }
}

let pay = once(function (money) {
  console.log(`支付￥${money}元`);
  return true;
})

// pay(5)
// pay(5);
// pay(5);
// pay(5);


/* 
### 使用高阶函数的意义
1.帮助我们屏蔽细节，只需要关注我们的目标
2.用于抽象通用的问题
*/

const map = (arr, fn) => {
  let results = [];
  for (let value of arr) {
    results.push(fn(value))
  }
  return results;
}

// let results = map(arr, (value) => {
//   return {
//     name: value
//   }
// })
// console.log(results)

const every = (arr, fn) => {
  let result = true;
  for (let value of arr) {
    result = fn(value);
    if (!result) {
      break;
    }
  }
  return result;
}

// let boolean = every(arr, v => v < 10);
// console.log(boolean)

const some = (arr, fn) => {
  let result = false;
  for (let value of arr) {
    result = fn(value);
    if (result) {
      break;
    }
  }
  return result;
}


// console.log(some(arr, v => v))
// console.log(arr.some(v => v)

const power = (power) => {
  return function (number) {
    return Math.pow(number, power)
    ``
  }
}


const power2 = power(2);
const power3 = power(3);
// console.log(power2(2))
// console.log(power2(3))
// console.log(power2(4))


/* 
### 纯函数:相同的输入始终得到相同的输出（纯函数）
**好处：**
1.可缓存 对相同的输入始终得到相同的结果，所以可以将结果缓存
2.可测试 纯函数让测试更方便
3.并行处理 在多线程的情况下操作共享数据可能出现意外。纯函数不用往问共享的数据内存，所以多线程环境可以任意使用纯函数（ES6 web worker支持多线程）。
**副作用：** 当纯函数依赖外部的状态，就会使得这纯函数不纯，无法保证相同的输入得到相同的输出。外部状态包括（数据库、用户输入、配置文件）
*/

// 记忆函数
function circArea(r) {
  console.log(r);
  return Math.PI * r * r;
}
const getcircArea = _.memoize(circArea);

// console.log(getcircArea(4))
// console.log(getcircArea(4))
// console.log(getcircArea(4))


// 模拟_.memoize函数
function memoize(fn) {
  let cache = {};
  return function (...arg) {
    return cache[arg.join()] = cache[arg.join] || fn(...arg);
  }
}

const getCirc = memoize(circArea)

// console.log(getCirc(4))
// console.log(getCirc(4))
// console.log(getCirc(4))

/* 
### 柯里化：当函数有多个参数时，可以先传递一部分参数调用它（这些参数以后不会变）的，返回一个新得函数接受剩余参数，并返回结果
*/

// lodash柯里化
function sum(a, b, c) {
  return a + b + c;
}

// const curry_sum = _.curry(sum);
// console.log(curry_sum(1,2,3))
// console.log(curry_sum(1)(2,3))
// console.log(curry_sum(1,2)(3))

const str_space = 'hello world! ';
const str_number = '1234aaa4321';

// function filterString(reg, str){
//   return str.match(reg);
// }

const filterString = _.curry((reg, str) => str.match(reg));
const filterSpace = filterString(/\s+/g);
const filterNumber = filterString(/\d+/g);
const filterWord = filterString(/[\u4e00-\u9fa5a-zA-Z]+/g);


// function fn1(){
//   console.log(Array.from(arguments))
// }
// fn1(1,23,23,4123,23)
// 模拟_.curry函数
function curry(fn) {
  return function _curry(...args) {
    if (fn.length > args.length) {
      return function () {
        return _curry(...[...args, ...arguments])
      }
    }
    return fn(...args);
  }
}

const curry_sum = curry(sum);
// console.log(curry_sum(1,2,3))
// console.log(curry_sum(1)(2,3))
// console.log(curry_sum(1,2)(3))

/* 
### 函数组合（compose）：
* 如果一个函数需要经过多个函数处理才能得到最终值，这个时候可以将多个函数合并成一个函数
* 函数就像数据得管道，函数组合就是把这些管道连接起来，让数据穿个多条管道得到最终结果
* 函数组合默认从右到左执行
*/

// const compose = function (f, g) {
//   return function (value) {
//     return f(g(value))
//   }
// }

// const reverse = (arr) => arr.reverse();
// const first = (arr) => arr[0];

// const testFn = compose(first, reverse);
// console.log(testFn([1, 2, 3, 4, 5]));

// const testFn = _.flowRight(_.toUpper, _.first, _.reverse);
// const numArr = ['one', 'two', 'three'];
// console.log(testFn(numArr));

// const compose = function(...args){
//   return function(value){
//     args.reduce(function(acc, fn){

//     }, value)
//   }
// }

// const arr_ = [0, 1, 2, 3, 4].reduce(function (accumulator, currentValue, currentIndex, array) {
//   console.log(accumulator, currentValue, currentIndex, array)
//   return accumulator + currentValue;
// }, 0);
// console.log(arr_)

// const arr_1 = [function(){console.log(0)},function(){console.log(1)},function(){console.log(2)},function(){console.log(3)}]
// console.log(arr_1.reduce((acc, fn) => {
//   console.log(acc)
//   return acc + 1;
// }, 0))

// const compose = function (...args) {
//   return function (value) {
//     return args.reverse().reduce(function (acc, fn) {
//       return fn(acc)
//     }, value)
//   }
// }

const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value);

const testFn = compose(_.toUpper, _.first, _.reverse);
const numArr = ['one', 'two', 'three'];
// console.log(testFn(numArr));

const uString = 'HOLLO WORLD JAVADSCRIPT';
// const split = _.curry((space, string) => _.split(string, space));
// const join = _.curry((string, arr) => _.join(arr, string));
// const map1 = _.curry((fn, arr) => _.map(arr, fn));
// // _.map(arr, function (item) {
// //   console.log(item)
// // })
const log = _.curry((tag, val) => {
  console.log(tag, val);
  return val;
})

// const fnT = _.flowRight(join('-'), log('map...'), map1(_.toLower), log('split...'), split(' '));
// // console.log(join('-')('1234'))
// console.log(fnT(uString));

const fnT = fp.flowRight(fp.join('-'), log('map'), fp.map(fp.toLower), log('split'), fp.split(' '));


const fnT1 = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toUpper, fp.trim);
// console.log(fnT1(str_space)) 

/* 
### point free:把数据得处理过程定义成与数据无关得合成运算。不需要用代表数据得那个参数，只把简单得运算步骤合成在一起，使用这种模式前，需要定义一些辅助得基本运算函数。
*/

const word = 'world wild web'; // => W. W. W

const fn2 = fp.flowRight(
  log('join'),
  fp.join('. '),
  log('map'),
  fp.map(fp.flow(fp.toUpper, fp.first)),
  log('split'),
  fp.split(' '),
  log('upper'),
  fp.toUpper
);

// console.log(fn2(word));

/* 
lodash/fp
loadsh/fp模块提供了实用的对函数式编程友好的方法
提供了不可变的**auto-curried**  **iteratee-first** **data-list**的方法
*/



/* 
### 函子（Functor）
**为什么要学习函子：**使函数式编程中的副作用控制在可控的范围内、异常处理、异步操作等
**什么是函子：**是一个特殊的容器（包含值和值的变形关系<这种关系就是函数>），通过一个普通的对象来实现，该对象有一个map方法。map方法可以执行一个函数对内部值进行修改
**总结：**
*函数式编程不直接操作值，而是函子完成。
*函子是实现map契约的对象。
*函子就是一个盒子，里面封装了一个值。
*想要处理这个值，通过map传一个纯函数，由这个函数来处理这个值。
*map最终返回一个函子对象
*/

class Container {

  static of (value) {
    return new Container(value);
  }

  constructor(value) {
    this._value = value;
  }

  map(fn) {
    return Container.of(fn(this._value));
  }
}

/* 
### MayBe函子：处理函子的空值
*/
class MayBe {
  static of (value) {
    return new MayBe(value)
  }

  constructor(value) {
    this._value = value
  }

  isNothing() {
    return this._value === null || this._value === undefined;
  }

  map(fn) {
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }
}
// console.log(MayBe.of(null).map(x => x.toString() ))

/* 
### Either函子：Either两者中的一个，类型if...else...的处理 。异常导致函数不纯，可以用来做异常处理 
*/
class Left extends Container {

  of (value) {
    return new Left(value);
  }

  constructor(value) {
    super(value)
  }

  map() {
    return this
  }
}

class Right extends Container {

  of (value) {
    return new Right(value);
  }

  constructor(value) {
    super(value)
  }

  map(fn) {
    return Right.of(fn(this._value));
  }
}

function parseJSON(str) {
  try {
    return Right.of(JSON.parse(str));
  } catch (err) {
    console.log(2)
    return Left.of({
      error: err.message
    })
  }
}

// console.log(parseJSON('{"name": "taoyang"}').map(x =>  x.name.toUpperCase()))


/* 
### IO函子
*IO函子中的_value是一个函数，把这个函数作为一个值来处理
*IO函子把不纯的动作存储在_value中，延迟执行这个不纯的操作（惰性执行）
*把不纯的操作交给调用者
*/



// console.log(new Container(5).map(x => x + 1).map(x => x * x)) 

const json = {
  "name": "code",
  "version": "1.0.0",
  "description": "",
  "main": "app.js"
}


/* 
### javascript 性能优化

### 内存管理介绍
**内存：**由可读写单元组成。表示一片可操作空间
**管理：**人为的操作一片空间的申请、使用、释放
**内存管理：**开发者主动申请空间、使用空间、释放空间
*/

/* 
### javascript中的垃圾
*javascript中内存管理是自动的
*如果一个对象没有被引用了，js引擎就会当这个对象是垃圾
*对象不能从根上访问都是垃圾
*/

/* 
### javascript中的可达对象
-可以往问的对象就是可达对象（引用、作用域链）
-可达的标准是从根出发
-js中的根就是全局对象
*/

/* 
### GC定义、作用
-GC就是垃圾回收机制的简写
-GC可以找到内存中的垃圾，并且释放、回收空间

### GC算法
-GC是一种机制，垃圾回收器完成具体的工作
-工作内容就是查找垃圾、释放空间、回收空间
-算法就是工作时查找、回收所遵循的规则

### 常见的GC算法
1.引用计数
2.标记清楚
3.标记整理
4.分代回收
*/



/* 
### 引用计数算法实现原理
-核心思想：设置引用数，判断当前引用数是否为0
-引用级数器
-引用关系改变时修改引用数字
-引用数字为0时立即回收
*/

/* 
### 引用计数算法优缺点
**优点**
1.发现垃圾时立即回收
2.最大限度减少程序暂停
**缺点**
1.无法回收循环引用的对象
2.时间开销大
*/

/* 
### 标记清除算法实现原理
-核心思想：分标记和清除两个阶段完成
-遍历所有对象标记活动对象（可达对象）
-遍历所有对象清除没有标记对象
-回收相应的空间
*/

/* 
### 标记清除算法优缺点
**优点**
1.可以回收循环引用对象
**缺点**
1.容易产生碎片化空间，浪费空间
2.不会立即回收垃圾对象
*/

/* 
### 标记整理算法实现原理
-标记整理可以看做是标记清除的增强
-标记阶段的操作和标记清除一致
-清除阶段会先执行整理，移动对象位置
**优点**
减少碎片化空间
**缺点**
不会立即回收垃圾对象
*/


/* 
### V8引擎
- V8引擎是主流的js执行引擎
- V8采用即时编译
- V8内存设限
*/

/* 
### V8垃圾回收策略
-采用分代回收的思想
-内存分为新生代、老生代
-针对不同对象采用不同算法（分代回收、空间复制、标记清除、标记整理、标记增量）
*/
